#include <windows.h>
#include <gl/glew.h>
#include <gl/glut.h>
#include <cmath>

#include "camera.h"
#include "scene.h"
#include "textures.h"

#include "lightfrustum.h"
#include "reflection.h"
#include "transparency.h"
#include "walls.h"
#include "environment.h"
#include "particles.h"

camera_t camera;
int mbd, mbx, mby;	// middle mouse button state
int pressed = 0;	// qwaszx keys state

using namespace Scene;
using namespace Textures;

Particles::ParticleSystem particleSystem;

matrix44 monitorTexTransfrom;

void mainDone();

void set_camera()
{
	camera.setProjection(60.0f, (float)w, (float)h, 0.1f, 1000.0f);
	glEnable(GL_CULL_FACE);
	glEnable(GL_DEPTH_TEST);
	glMatrixMode(GL_PROJECTION);
	matrix44 mProj = camera.getProjectionMatrix();
	matProj = mProj;
	glLoadMatrixf((float*)(&mProj));
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	matrix44 mView = camera.getViewMatrix();
	matView = mView;
	camera_pos  = InvertMatrix44(mView)*vector4(0,0,0,1);
	glLoadMatrixf((float*)(&mView));
}

void set_light()
{
	GLfloat ambientLight[] = {0.1f, 0.1f, 0.1f, 1.0f};
	GLfloat diffuseLight[] = {0.3f, 0.3f, 0.3f, 1.0f};
	GLfloat specularLight[] = {0.2f, 0.2f, 0.2f, 1.0f};
	vector4 posLight0(camera_pos.x, camera_pos.y, camera_pos.z, 0.0f);
	vector4 posLight1(1.0f,1.0f,1.0f,1.0f);
	vector4 posLight2(-1.0f,-1.0f,-1.0f,1.0f);
	glEnable(GL_LIGHTING);
	glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
	glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
	glLightfv(GL_LIGHT0, GL_POSITION, &(posLight0.x));
	glDisable(GL_LIGHT0);
	glLightfv(GL_LIGHT1, GL_AMBIENT, ambientLight);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuseLight);
	glLightfv(GL_LIGHT1, GL_SPECULAR, specularLight);
	glLightfv(GL_LIGHT1, GL_POSITION, &(posLight1.x));
	glEnable(GL_LIGHT1);
	glLightfv(GL_LIGHT2, GL_AMBIENT, ambientLight);
	glLightfv(GL_LIGHT2, GL_DIFFUSE, diffuseLight);
	glLightfv(GL_LIGHT2, GL_SPECULAR, specularLight);
	glLightfv(GL_LIGHT2, GL_POSITION, &(posLight2.x));
	glEnable(GL_LIGHT2);
}

void render_scene()
{
	glGetFloatv(GL_MODELVIEW_MATRIX, (float*)(&Scene::matView[0][0]));
	Scene::camera_pos  = InvertMatrix44(Scene::matView)*vector4(0,0,0,1);
	set_light();

	// 1. walls
	Walls::draw_box();
	
	// 2. teapot
	//  Environment::debug();
	//	Environment::debug2();
	glColor3f(.8f,.7f,.65f);
	Environment::begin();
	glPushMatrix();
	glMultMatrixf(&(matrix_teapot[0][0]));
	glCallList(teapot);
	glPopMatrix();
	Environment::end();
	glColor4f(1,1,1,1);

	// 3. shelf
	glPushMatrix();
	glMultMatrixf(&(matrix_shelf[0][0]));
	glCallList(shelf);
	glPopMatrix();

	// 4. monitor + screen
	glPushMatrix();
	glMultMatrixf(&(matrix_monitor[0][0]));
	monitor.draw();

	glMatrixMode(GL_TEXTURE);
	glPushMatrix();
	glLoadMatrixf((float*)(&monitorTexTransfrom));
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, tex_screen);
	screen.draw();
	glDisable(GL_TEXTURE_2D);

	glMatrixMode(GL_TEXTURE);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	
	glPopMatrix();

	// 5. chair
	glPushMatrix();
	glMultMatrixf(&(matrix_chair[0][0]));
	chair_back.draw();
	chair_seat.draw();
	//chair_back.glow(.02f);
	glPopMatrix();

	// 6. lamp
	glPushMatrix();
	glMultMatrixf(&(matrix_lamp[0][0]));
	lamp.draw();
	glPopMatrix();

	// Transparent object must be drawn at the end
	Transparency::render_transparent_objects(mirror);

		//7. particles
	glFrontFace(GL_CCW);
	glCullFace(GL_BACK);
	particleSystem.render_particles(Scene::camera_pos, Scene::matView);	
}

void display()
{
	glClearColor(1.0f,1.0f,1.0f,1.0f);

	set_camera();
	Environment::update_next_face(render_scene);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	set_camera();
	glColor3f(1,1,1);
	
	Reflection::render_reflection(render_scene);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	render_scene();

	// mapowanie srodowiska monitor
	glBindTexture(GL_TEXTURE_2D, tex_screen);
	glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, w, h, 0); 

	glutSwapBuffers();
}

void idle()
{

	static DWORD time0 = GetTickCount();
	static DWORD time1 = GetTickCount();
	DWORD time = GetTickCount();
	float dt = (time-time0)/1000.0f;
	if (dt > .1f)
		dt = .1f;
	if (dt < 0.001f)
		dt = .001f;
	time0 = time;

	Scene::update_lamp(time-time1);
	particleSystem.update_particles(dt);

	if (pressed)
		camera.kbdProc(pressed);


	glutPostRedisplay();

}

void reshape(int _w, int _h)
{
	w = _w;
	h = _h;
	Textures::empty_texture(Textures::tex_reflect, w, h);
	Textures::empty_texture(Textures::tex_screen, w, h);
	glViewport(0,0,w,h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-1,1,-1,1,-1,1);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void mouse(int button, int state, int x, int y)
{
	switch(button)
	{
	case GLUT_LEFT_BUTTON:
		if(state == GLUT_DOWN)
		{
			camera.mouseProc(camera_t::LBUTTONDOWN, x, y);
		}
		else
		{
			camera.mouseProc(camera_t::LBUTTONUP, x, y);
		}
		break;
	case GLUT_MIDDLE_BUTTON:
		if (state == GLUT_DOWN)
		{
			mbd = 1;
			mbx = x;
			mby = y;
		}
		else
		{
			mbd = 0;
		}
		break;
	case GLUT_RIGHT_BUTTON:
		if(state == GLUT_DOWN)
		{
			camera.mouseProc(camera_t::RBUTTONDOWN, x, y);
		}
		else
		{
			camera.mouseProc(camera_t::RBUTTONUP, x, y);
		}
		break;
	}
	glutPostRedisplay();
}

void mousemove(int x, int y)
{
	camera.mouseProc(camera_t::MOUSEMOVE, x, y);
	if (mbd)
	{
		Textures::lod_bias += static_cast<float>(mby-y)/100.0f;
		if (Textures::lod_bias > 5.0f)
			Textures::lod_bias = 5.0f;
		if (Textures::lod_bias < -5.0f)
			Textures::lod_bias = -5.0f;
		mby = y;
	}
	glutPostRedisplay();
}

void keyboard(unsigned char key, int x, int y)
{
	switch (key)
	{
	case 27:
	case 'q':
	case 'Q':
		mainDone();
         exit(0);
	case ' ':
		camera.switchFreeMode();
		break;
	case 'w':
		pressed |= 0x01;
		break;
	case 's':
		pressed |= 0x02;
		break;
	case 'd':
		pressed |= 0x04;
		break;
	case 'a':
		pressed |= 0x08;
		break;
	/*case 'q':
		pressed |= 0x10;
		break;*/
	case 'z':
		pressed |= 0x20;
		break;
	}
	camera.kbdProc(pressed);

}

void keyboardUp(unsigned char key, int x, int y)
{
	switch (key)
	{
	case 'w':
		pressed &= ~0x01;
		break;
	case 's':
		pressed &= ~0x02;
		break;
	case 'd':
		pressed &= ~0x04;
		break;
	case 'a':
		pressed &= ~0x08;
		break;
	case 'q':
		pressed &= ~0x10;
		break;
	case 'z':
		pressed &= ~0x20;
		break;
	}
	camera.kbdProc(pressed);
}


void mainInit()
{
	Textures::init();
	Scene::init();
	Transparency::init();
	Environment::init(vector3(-1.3f,-.6f,-0.74f), .4f, 8.0f);

	monitorTexTransfrom = TranslateMatrix44(0.5f, 0.5f, 0.0f) * ScaleMatrix44(-1.0, -1.0, 1.0) * TranslateMatrix44(-0.5f, -0.5f, 0.0f);// * ;

	GLfloat ambiref[] = {0.3f, 0.3f, 0.3f, 1.0f};
	GLfloat diffref[] = {0.5f, 0.5f, 0.5f, 1.0f};
	GLfloat specref[] = {1.0f, 1.0f, 1.0f, 1.0f};
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambiref);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffref);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specref);
	glMateriali(GL_FRONT_AND_BACK, GL_SHININESS, 128);
	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
	glDepthFunc(GL_LEQUAL);
	glFrontFace(GL_CCW);
	glCullFace(GL_BACK);
	glEnable(GL_NORMALIZE);
}

void mainDone()
{
	Environment::done();
	Textures::done();
	Scene::done();
}

int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitWindowSize(800, 800);
	glutInitDisplayString("alpha>=8 rgb stencil>=8 double depth>=24");
	glutCreateWindow("TheRoom");
	glutDisplayFunc(display);
	glutMouseFunc(mouse);
	glutMotionFunc(mousemove);
	glutKeyboardFunc(keyboard);
	glutKeyboardUpFunc(keyboardUp);
	glutReshapeFunc(reshape);
	glutIdleFunc(idle);
	glewInit();
	mainInit();
	glutMainLoop();
	mainDone();
	return 0;
}